#!/usr/bin/env python

from __future__ import print_function

import copy, optparse, os, signal, subprocess, sys, time

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, 'test', 'common')))
import driver, utils

# == signal handler

keepRunning = True
def sigintHandler(signal, frame):
    global keepRunning
    print('\nShutting down servers')
    keepRunning = False
signal.signal(signal.SIGINT, sigintHandler)

# == main

def main():
    
    # -- parse input
    
    parser = optparse.OptionParser()
    parser.add_option('-b', '--binary',     dest='binary',     metavar='EXECUTABLE',   default=None)
    parser.add_option(      '--cache-size', dest='cache_size', metavar='MEGABYTES',    default=None, type="int")
    parser.add_option(      '--cores',      dest='cores',      metavar='COUNT',        default=None, type="int")
    parser.add_option(      '--direct-io',  dest='direct_io',  action='store_true',    default=False)
    parser.add_option(      '--io-threads', dest='io_threads', metavar='COUNT',        default=None, type="int")
    parser.add_option('-o', '--output-dir', dest='output_dir', metavar='DIRNAME',      default=None)
    parser.add_option('-s', '--servers',    dest='servers',    metavar='COUNT',        default=2,    type="int")
    parser.add_option(      '--tls',        dest='tls',        action='store_true',    default=False)
    options, args = parser.parse_args()
    
    options.valgrind = False
    
    # -- validate options
    
    if options.binary is not None:
        if os.path.isfile(options.binary) and os.access(options.binary, os.X_OK):
            options.binary = os.path.realpath(options.binary)
            if 'valgrind' in os.path.basename(os.path.dirname(options.binary)):
                options.valgrind = True
        else:
            parser.error('-b/--binary does not exist or is not runnable')
    else:
        try:
            options.binary = utils.find_rethinkdb_executable()
        except Exception as e:
            parser.error(str(e))
    
    if options.cores is not None and options.cores < 1:
        parser.error('--cores must be greater than 1')
    
    if options.servers is not None and options.servers < 1:
        parser.error('--servers must be greater than 1')
    
    # -- setup the options
    
    # - options
    
    extraOptions = []
    if options.cache_size:
        extraOptions += ['--cache-size', str(options.cache_size)]
    
    if options.cores:
        extraOptions += ['--cores', str(options.cores)]
    
    if options.direct_io:
        extraOptions += ['--direct-io']
    
    if options.io_threads:
        if options.io_threads < 1:
            parser.error('--io-threads must be 1 or greater')
        extraOptions += ['--io-threads', str(options.io_threads)]
    
    if options.output_dir:
        if os.path.exists(options.output_dir) and not os.path.isdir(options.output_dir):
            parser.error('A file already existed at the -o/--output-dir location: %s' % options.output_dir)
        options.output_dir = os.path.realpath(options.output_dir)
        if not os.path.exists(os.path.dirname(options.output_dir)):
            parser.error('The parent directory of -o/--output-dir does not exist: %s' % os.path.dirname(options.output_dir))
        if not os.path.exists(options.output_dir):
            try:
                os.mkdir(options.output_dir)
            except Exception as e:
                parser.error('Unable to create ouput dir: %s' % e)
    
    # - command prefix
    
    commandPrefix = []
    if options.valgrind:
        print('Valgrind build detected, using it')
        commandPrefix = ['valgrind', '--leak-check=full', '--track-origins=yes', '--suppressions=' + os.path.join(__file__, 'rethinkdb-valgrind-suppressions.supp')]
    
    # -- print the server information
    
    print(options.binary)
    versionString = ''
    try:
        versionString, _ = subprocess.Popen([options.binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
    except Exception as e:
        parser.error('Unable to get RethinkDB version: %s' % str(e))
    print('Using rethinkdb binary from: %s\n\tversion:  %s' % (options.binary, versionString.strip()))
    
    # -- start the cluster
    
    cluster = None
    try:
        cluster = driver.Cluster(
            initial_servers = options.servers,
            output_folder   = options.output_dir,
            console_output  = True,
            executable_path = options.binary,
            command_prefix  = commandPrefix,
            extra_options   = extraOptions,
            tls             = options.tls
        )
    except Exception as e:
        parser.error('Starting the cluster failed! %s' % str(e))
    
    # - print the cluster information
    
    print('\t%sdata dir: %s' % ('' if options.output_dir else 'temporary ', cluster.output_folder))
    if options.tls:
        print('\tcert:     %s' % cluster.tlsCertPath)
    
    for server in cluster:
        print('''\
Server %(name)s (pid: %(pid)d)
    data folder:  %(folder)s
    cluster port: %(cluster)d
    driver port:  %(driver)d
    web admin:    http%(secure)s://%(host)s:%(http)s''' % {
                'name':server.name,
                'pid':server.pid,
                'folder':server.data_path,
                'cluster':server.cluster_port,
                'driver':server.driver_port,
                'host':server.host,
                'secure':'s' if options.tls else '',
                'http':server.http_port
            })
    
    print('To shut down servers use ctl-c')
    
    # -- wait for cluster to close, or cmd-c
    
    monitoredServers = cluster[:] # copy the list
    while keepRunning:
        if monitoredServers:
            time.sleep(0.2)
            for server in copy.copy(monitoredServers):
                if not server.running:
                    if server.returncode == 0:
                        print('Server %s exited cleanly' % server.name)
                    else:
                        consoleOutput = server.console_output
                        print('\nServer %s exited with code: %r%s' % (server.name, server.returncode, '\n<<<<<<<<\n' + consoleOutput + ">>>>>>>>\n" if consoleOutput else ''))
                    monitoredServers.remove(server)
        else:
            if options.output_dir:
                print('No servers still running')
            else:
                print('No servers still running, waiting for ctl-c to keep files')
                if hasattr(signal, 'pause'):
                    signal.pause()
                else:
                    while True:
                        time.sleep(10)
    try:
        cluster.check_and_stop()
    except Exception: pass

if __name__ == '__main__':
    main()
